Supabaseでservice_roleを使わずanon+RLS+SSR_EdgeFunctions で構成する

作成日
更新日

#ChatGPTより引用

素晴らしい判断です。
service_role を使わずに anon キー + RLS + SSR / Edge Functions で構成するアーキテクチャは、Supabase をセキュアかつスケーラブルに運用する現代的なベストプラクティスです。


✅ ゴールの構成

[Client] ────▶ [Deno Fresh SSR or EdgeFunction] ────▶ Supabase (anon key + RLS enforced)
要素 内容
🔐 anon キー パブリック用、SupabaseにRLS付きで最小限アクセスさせる
🛡️ RLS(Row Level Security) private = false など行単位で公開制御
⚙️ SSR / Edge Function クライアントからはDB接続を隠蔽。Fresh内でfetch or Supabase SDKを使って中継
service_role 一切使わない(リーク対策・アクセス最小化)

✅ ステップごとの構成方法


✅ ステップ 1:Supabase 側の RLS ポリシー設定

例:articles テーブルを「private = false の記事だけ誰でも取得可能」に

-- RLSを有効化
alter table articles enable row level security;

-- 誰でも読めるポリシー(anonキーでもOK)
create policy "Public access to non-private articles"
  on articles
  for select
  using (private = false);

anon キーはデフォルトで authenticated ロールになるので、auth.uid() は使えません(public用途なら問題なし)


✅ ステップ 2:環境変数に anon キーだけを保持

.env

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOi...(anon key)

✅ ステップ 3:Fresh SSR 側で Supabase を呼び出す

例:routes/index.tsx にて SSR で Supabase から記事一覧取得

// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
import { createClient } from "supabase";

const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const supabase = createClient(supabaseUrl, supabaseAnonKey);

export const handler: Handlers = {
  async GET(req, ctx) {
    const { data, error } = await supabase
      .from("articles")
      .select("title, slug, created_at")
      .eq("private", false)
      .order("created_at", { ascending: false });

    if (error) console.error(error);

    return ctx.render({ articles: data ?? [] });
  },
};

export default function HomePage({ data }: PageProps<{ articles: any[] }>) {
  return (
    <div>
      <h1>Articles</h1>
      <ul>
        {data.articles.map((article) => (
          <li key={article.slug}>{article.title}</li>
        ))}
      </ul>
    </div>
  );
}

👉 クライアントから直接Supabaseへアクセスしていないので、漏洩リスクなし。


✅ ステップ 4(任意):Edge Functionでの中継にする(API風)

routes/api/articles.ts

import { Handlers } from "$fresh/server.ts";
import { createClient } from "supabase";

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_ANON_KEY")!
);

export const handler: Handlers = {
  async GET(req) {
    const { data, error } = await supabase
      .from("articles")
      .select("title, slug, created_at")
      .eq("private", false)
      .order("created_at", { ascending: false });

    return new Response(JSON.stringify(data ?? []), {
      headers: { "content-type": "application/json" },
    });
  },
};

→ クライアントは /api/articles を叩くだけ。Supabaseへの直接アクセスは一切なし。


✅ この構成のメリット

メリット 内容
🔒 セキュア service_role 不使用、anon + RLSだけで安全運用
👁️‍🗨️ アプリにキーが露出しない クライアントには anon すら渡さない
⚙ SSR対応 Freshの静的化・キャッシュと相性良好
🧱 スケーラブル Edge Function構成で将来API分離も可能

✅ まとめ:今後の運用設計の型

📁 .env
    SUPABASE_URL
    SUPABASE_ANON_KEY

📁 routes/
    index.tsx          ← SSRでSupabase叩く
    api/articles.ts    ← Edge Functionでも対応可(SPA用途)
📁 supabase/
    RLSポリシーで制御(service_role使わない)

✅ middleware.ts        ← Bot防御・旧URL対策など

必要であれば:

  • RLSをロール別に拡張する例(user_idベース)
  • コメント付きSupabase設計スキーマ
  • Supabase Authと組み合わせたJWTバインド構成

などもご案内できます。ぜひお気軽に!

サイトアイコン
公開日
更新日